/*
  main.c
  The main framework of the BookListBuilder
  BeginnerC
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <unistd.h>
#include "moudle/command/command.h"
#include "moudle/config/config.h"
#include "moudle/file/file.h"
/*
  BookListItem
    Describle the book item
  BeginnerC
*/
typedef struct BookListItem
{
    char file_name[NAME_MAX];
    char tag[NAME_MAX];
    char title[NAME_MAX];
    struct BookListItem *next;
}BookListItem;
/*
  Help
    Print the help message
  Argument
    argument
      Will be ignored
  Return Value
    0 Everything OK
  BeginnerC
*/
int Help(void *argument)
{
    puts("+ init");
    puts("  初始化一个新的空文件夹");
    puts("+ build");
    puts("  根据配置文件中的内容，构建网站");
    puts("+ version");
    puts("  输出 BookListBuilder 软件版本");
    puts("+ help");
    puts("  输出 BookListBuilder 内置帮助");
    return 0;
}
/*
  Version
    Print the version message
  Argument
    argument
      Will be ignored
  Return Value
    0 Everything OK
  BeginnerC
*/
int Version(void *argument)
{
    puts("BookListBuilder 1.0.0");
    puts("BeginnerC");
    return 0;
}
/*
  Init
    Init a directory
  Argument
    argument
      The path to the target
  Return Value
    0 Everything OK
    -1 Something wrong
  BeginnerC
*/
int Init(void *argument)
{
    MainArgument *main_argument = (MainArgument*)argument;
    char target_path[NAME_MAX] = {};
    if (3 != main_argument -> argc)
    {
        printf("Usage: BookListBuilder init [path]\n");
        return -1;
    }
    strncpy(target_path, main_argument -> argv[2], NAME_MAX);
    DirectoryRemove(target_path);
    mkdir(target_path, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
    strcat(target_path, "/default");
    DirectoryCopy("./default", target_path);
    strncpy(target_path, main_argument -> argv[2], NAME_MAX);
    strcat(target_path, "/books");
    mkdir(target_path, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
    return 0;
}
/*
  AnalyseDirectoryName
    Process the file name to tag & title
  Argument
    file_name
      The name of file
  Return Value
    The pointer to the item
    NULL Failed
*/
/*
  file name format
  [tag]-[title].suffix_name
*/
BookListItem* AnalyseDirectoryName(const char *file_name)
{
    BookListItem* result = NULL;
    /* The first ']' appear in the file name */
    char *tag_1 = NULL;
    /* The second '[' appear in the file name */
    char *tag_2 = NULL;
    /* The second ']' appear in the file name */
    char *tag_3 = NULL;
    if (NULL == file_name || '[' != *file_name)
    {
        return false;
    }
    result = (BookListItem*)calloc(1, sizeof(BookListItem));
    if (NULL == result)
    {
        return NULL;
    }
    tag_1 = strchr(file_name, ']');
    if (NULL == tag_1)
    {
       free(result);
       return NULL;
    }
    tag_2 = strchr(tag_1, '[');
    if (NULL == tag_2)
    {
       free(result);
       return NULL;
    }
    tag_3 = strrchr(tag_2, ']');
    if (NULL == tag_3)
    {
        free(result);
        return NULL;
    }
    strncpy(result -> tag, file_name + 1, tag_1 - file_name - 1);
    strncpy(result -> title, tag_2 + 1, tag_3 - tag_2 - 1);
    return result;
}
/*
  StringConnect
    Connect two string
  Argument
    target
      The target string
    source
      The substr want to connect
  Return Value
    The new string
    NULL failed
  BeginnerC
*/
char* StringConnect(char *target, char *source)
{
    char *result = NULL;
    if (NULL == target || NULL == source)
    {
        return NULL;
    }
    result = (char*)calloc(strlen(target) + strlen(source) + 1, sizeof(char));
    if (NULL == result)
    {
        return NULL;
    }
    strcat(result, target);
    strcat(result, source);
    return result;
}
/*
  StringReplace
    replace str needle
  Argument
    source
      The source string
    needle
      The substr want to replace
    replacement
      The relace string to the needle
  Return Value
    The new string
    NULL failed
  BeginnerC
*/
char* StringReplace(char *source, char *needle, char *replacement)
{
    char *result = NULL;
    char *temp = NULL, *temp_2 = NULL;
    char *begin = NULL;
    int needle_len = 0, replacement_len = 0;
    int size = 0;
    int diff = 0;
    int count = 0;

    needle_len = strlen(needle);
    replacement_len = strlen(replacement);
    temp = source;
    size = strlen(source);
    diff = replacement_len - needle_len;
    while (temp = (char*)strstr(temp, needle))
    {
        count++;
        temp += needle_len;
    }
    size += count * diff;

    if (0 == count)
    {
        return source;
    }
    result = (char*)calloc(size + 1, sizeof(char));
    if (NULL == result)
        return NULL;

    begin = source;
    temp = source;
    temp_2 = result;
    while (temp = (char*)strstr(temp, needle))
    {
        memcpy(result, begin, temp - begin);
        result += (temp - begin);
        strcat(result, replacement);
        result += replacement_len;
        begin = temp + needle_len;
        temp += needle_len;
    }
    strcat(result, begin);
    result = temp_2;
    free(source);

    return result;
}
/*
  BookListSort
    Sort the book list item
  Argument
    element_1
      The first item
    element_2
      The second item
  Return Value
    Equal to strcmp
  BeginnerC
*/
int BookListSort(const void *element_1, const void *element_2)
{
    BookListItem *item_1 = (BookListItem*)element_1, *item_2 = (BookListItem*)element_2;
    return strcmp(item_1 -> tag, item_2 -> tag);
}
/*
  Build
    Build the website
  Argument
    argument
      Will be ignored
  Return Value
    0 Everything OK
    -1 Something wrong
  BeginnerC
*/
int Build(void *argument)
{
    MainArgument *main_argument = (MainArgument*)argument;
    char target_file[NAME_MAX] = {};
    char *target_path = NULL;
    Config* config = NULL;
    BookListItem *first = NULL, *last = NULL, *temp = NULL;
    int count;
    void **book_list = NULL;
    int result = 0;
    if (3 != main_argument -> argc)
    {
        result = -1;
        printf("Usage: BookListBuilder build [path]\n");
        return result;
    }
    target_path = main_argument -> argv[2];
    /* Read the config */
    sprintf(target_file, "%s/default/config/config.ini", target_path);
    config = LoadConfig(target_file);
    if (NULL == config)
    {
       result = -1;
       printf("Error: Can't open the config file\n");
       return result;
    }
    /* Traverse the directory */
    sprintf(target_file, "%s/books", target_path);
    {
        DIR *directory = NULL;
        struct dirent *file = NULL;
        if (NULL == (directory = opendir(target_file)))
        {
            FreeConfig(config);
            result = -1;
            return result;
        }
        while (NULL != (file = readdir(directory)))
        {
            if (DT_DIR == file -> d_type)
            {
                if (!strcmp(file -> d_name, ".") || !strcmp(file -> d_name, ".."))
                {
                    continue;
                }
                temp = AnalyseDirectoryName(file -> d_name);
                if (NULL == temp)
                {
                    /* Just ignored */
                    continue;
                }
                if (NULL == first)
                {
                    first = temp;
                    last = temp;
                }
                else
                {
                    last -> next = temp;
                    last = temp;
                }
                count++;
            }
            else
            {
                continue;
            }
        }
    }
    if (0 == count)
    {
        printf("Error: No book prepared.\n");
        FreeConfig(config);
        result = -1;
        return result;
    }
    book_list = calloc(count, sizeof(void*));
    if (NULL == book_list)
    {
        FreeConfig(config);
        while (first)
        {
            temp = first -> next;
            free(first);
            first = temp;
        }
        result = -1;
        return result;
    }
    temp = first;
    for (int i = 0;i < count;i++)
    {
        book_list[i] = (void*)temp;
        temp = temp -> next;
    }
    qsort(book_list, count, sizeof(void*), BookListSort);
    /* Create the target directory */
    sprintf(target_file, "%s/../%s", target_path, QueryConfig(config, "name"));
    DirectoryRemove(target_file);
    mkdir(target_file, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
    /* Fork the work flow */
    /* Child process */
    if (0 == fork())
    {
        for (int i = 0;i < count;i++)
        {
            char command[1024] = {};
            char source_file[NAME_MAX] = {};
            char target_file[NAME_MAX] = {};
            sprintf(command, "./mdbook build %s/books/[%s]-[%s]", target_path, ((BookListItem*)book_list[i]) -> tag, ((BookListItem*)book_list[i]) -> title);
            system(command);
            sprintf(source_file, "%s/books/[%s]-[%s]/book", target_path, ((BookListItem*)book_list[i]) -> tag, ((BookListItem*)book_list[i]) -> title);
            sprintf(target_file, "%s/../%s/[%s]-[%s]", target_path, QueryConfig(config, "name"), ((BookListItem*)book_list[i]) -> tag, ((BookListItem*)book_list[i]) -> title);
            DirectoryCopy(source_file, target_file);
        }
    }
    else
    {
        char *content = NULL;
        char *list_content = NULL;
        sprintf(target_file, "%s/default/template/list.html", target_path);
        /* We assume the line length is < 1024 */
        list_content = (char*)calloc(count, 1024);
        if (NULL == list_content)
        {
            result = -1;
            goto END;
        }
        content = FileContent(target_file);
        if (NULL == content)
        {
            result = -1;
            goto END;
        }
        content = StringReplace(content, "{name}", QueryConfig(config, "name"));
        if (NULL == content)
        {
            result = -1;
            goto END;
        }
        content = StringReplace(content, "{title}", QueryConfig(config, "title"));
        if (NULL == content)
        {
            result = -1;
            goto END;
        }
        content = StringReplace(content, "{introduction}", QueryConfig(config, "introduction"));
        if (NULL == content)
        {
            result = -1;
            goto END;
        }
        {
            char *temp = NULL;
            char *tag = ((BookListItem*)book_list[0]) -> tag;
            strcat(list_content, "<ul>「");
            strcat(list_content, tag);
            strcat(list_content, "」");
            for (int i = 0;i < count;i++)
            {
                char buffer[1024] = {};
                if (strcmp(tag, ((BookListItem*)book_list[i]) -> tag))
                {
                    strcat(list_content, "</ul>\n");
                    strcat(list_content, "<ul>「");
                    strcat(list_content, ((BookListItem*)book_list[i]) -> tag);
                    strcat(list_content, "」");
                    tag = ((BookListItem*)book_list[i]) -> tag;
                }
                strcat(list_content, "\t<li>");
                sprintf(buffer, "<center><h3><a href = \"./[%s]-[%s]/index.html\">《%s》</a></h3></center>", ((BookListItem*)book_list[i]) -> tag, ((BookListItem*)book_list[i]) -> title, ((BookListItem*)book_list[i]) -> title);
                strcat(list_content, buffer);
                strcat(list_content, "</li>\n");
            }
        }
        content = StringReplace(content, "{book_list}", list_content);
        free(list_content);
        if (NULL == content)
        {
            result = -1;
            goto END;
        }
        sprintf(target_file, "%s/../%s/index.html", target_path, QueryConfig(config, "name"));
        WriteFile(target_file, content);
        free(content);
    }
    /* Clean up the resource */
 END:
    FreeConfig(config);
    while (first)
    {
        temp = first -> next;
        free(first);
        first = temp;
    }
    free(book_list);
    return result;
}
int main(int argc, char *argv[])
{
    CommandList *command_list = NULL;
    MainArgument argument = {};
    argument.argc = argc;
    argument.argv = argv;
    if (2 > argc)
    {
        return Help(NULL);
    }
    command_list = InitList();
    if (NULL == command_list)
    {
        printf("Error: Init the command list failed.\n");
        return -1;
    }
    BindCommand(command_list, "help", Help);
    BindCommand(command_list, "version", Version);
    BindCommand(command_list, "init", Init);
    BindCommand(command_list, "build", Build);
    HandleCommand(command_list, argv[1], (void*)&argument);
    FreeList(command_list);
    return 0;
}
